/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.activiti.explorer.ui.custom;

import com.vaadin.data.Item;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.FieldEvents.TextChangeListener;
import com.vaadin.ui.AbstractTextField.TextChangeEventMode;
import com.vaadin.ui.*;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.themes.Reindeer;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.identity.User;
import org.activiti.explorer.ExplorerApp;
import org.activiti.explorer.I18nManager;
import org.activiti.explorer.Messages;
import org.activiti.explorer.identity.LoggedInUser;
import org.activiti.explorer.ui.Images;
import org.activiti.explorer.ui.event.SubmitEvent;
import org.activiti.explorer.ui.event.SubmitEventListener;
import org.activiti.explorer.ui.util.ThemeImageColumnGenerator;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;


/**
 * A popup window that is used to select people. Two possible modes:
 * - multiselect: displays two tables that allow to select users from the left table
 * to the table on the right
 * - non-multiselect: one table where only one user can be chosen from.
 * <p>
 * {@link SubmitEventListener} can be attached to listen to completion of the
 * selection. The selected user(s) can be retrieved using {@link #getSelectedUserId()}
 * ,{@link #getSelectedUserIds()} and {@link #getSelectedUserRole(String)}.
 *
 * @author Joram Barrez
 */
public class SelectUsersPopupWindow extends PopupWindow {

    private static final long serialVersionUID = 1L;

    protected String title;
    protected boolean multiSelect = true;
    protected boolean showRoles = true;
    protected Collection<String> ignoredUserIds;

    protected I18nManager i18nManager;

    protected VerticalLayout windowLayout;
    protected TextField searchField;
    protected HorizontalLayout userSelectionLayout;
    protected Table matchingUsersTable;
    protected Button selectUserButton;
    protected Table selectedUsersTable;
    protected Button doneButton;

    public SelectUsersPopupWindow(String title, boolean multiSelect) {
        this.title = title;
        this.multiSelect = multiSelect;
        this.i18nManager = ExplorerApp.get().getI18nManager();
    }

    public SelectUsersPopupWindow(String title, boolean multiSelect, Collection<String> ignoredUserIds) {
        this(title, multiSelect);
        this.ignoredUserIds = ignoredUserIds;
    }

    public SelectUsersPopupWindow(String title, boolean multiSelect, boolean showRoles, Collection<String> ignoredUserIds) {
        this(title, multiSelect);
        this.showRoles = showRoles;
        this.ignoredUserIds = ignoredUserIds;
    }

    @Override
    public void attach() {
        super.attach();
        initUi();
    }

    protected void initUi() {
        setCaption(title);
        setModal(true);
        addStyleName(Reindeer.WINDOW_LIGHT);
        center();

        windowLayout = (VerticalLayout) getContent();
        windowLayout.setSpacing(true);

        if (multiSelect && showRoles) {
            setWidth(820, UNITS_PIXELS);
        } else if (multiSelect && !showRoles) {
            setWidth(685, UNITS_PIXELS);
        } else {
            setWidth(340, UNITS_PIXELS);
        }
        setHeight(350, UNITS_PIXELS);

        initSearchField();
        initUserSelection();
        initDoneButton();
    }

    protected void initSearchField() {
        HorizontalLayout searchLayout = new HorizontalLayout();
        searchLayout.setSpacing(true);
        addComponent(searchLayout);

        // textfield
        searchField = new TextField();
        searchField.setInputPrompt(i18nManager.getMessage(Messages.PEOPLE_SEARCH));
        searchField.setWidth(180, UNITS_PIXELS);
        searchField.focus();
        searchField.setTextChangeEventMode(TextChangeEventMode.EAGER);
        searchLayout.addComponent(searchField);

        // Logic to change table according to input
        searchField.addListener(new TextChangeListener() {
            public void textChange(TextChangeEvent event) {
                searchPeople(event.getText());
            }
        });

        initSelectMyselfButton(searchLayout);
    }

    protected void initSelectMyselfButton(HorizontalLayout searchLayout) {
        final LoggedInUser loggedInUser = ExplorerApp.get().getLoggedInUser();
        if (ignoredUserIds == null || !ignoredUserIds.contains(loggedInUser.getId())) {
            Button meButton = new Button(i18nManager.getMessage(Messages.PEOPLE_SELECT_MYSELF));
            meButton.setIcon(Images.USER_16);
            searchLayout.addComponent(meButton);
            searchLayout.setComponentAlignment(meButton, Alignment.MIDDLE_LEFT);

            if (multiSelect) {
                meButton.addListener(new ClickListener() {
                    public void buttonClick(ClickEvent event) {
                        selectUser(loggedInUser.getId(), loggedInUser.getFullName());
                    }
                });
            } else {
                meButton.addListener(new ClickListener() {
                    public void buttonClick(ClickEvent event) {
                        addMatchingUser(loggedInUser.getId(), loggedInUser.getFullName());
                        matchingUsersTable.select(loggedInUser.getId());
                        fireEvent(new SubmitEvent(doneButton, SubmitEvent.SUBMITTED));
                        close();
                    }
                });
            }
        }
    }

    protected void searchPeople(String searchText) {
        if (searchText.length() >= 2) {
            matchingUsersTable.removeAllItems();

            List<User> results = ProcessEngines.getDefaultProcessEngine()
                    .getIdentityService()
                    .createUserQuery()
                    .userFullNameLike("%" + searchText + "%")
                    .list();

            for (User user : results) {
                if (!multiSelect || !selectedUsersTable.containsId(user.getId())) {
                    if (ignoredUserIds == null || !ignoredUserIds.contains(user.getId())) {
                        addMatchingUser(user.getId(), user.getFirstName() + " " + user.getLastName());
                    }
                }
            }
        }
    }

    protected void addMatchingUser(String userId, String name) {
        if (!matchingUsersTable.containsId(userId)) {
            Item item = matchingUsersTable.addItem(userId);
            item.getItemProperty("userName").setValue(name);
        }
    }

    protected void initUserSelection() {
        userSelectionLayout = new HorizontalLayout();
        userSelectionLayout.setSpacing(true);
        addComponent(userSelectionLayout);

        initMatchingUsersTable();

        // If multi select: two table to move users from left to the right
        // non-multi select: only one table
        if (multiSelect) {
            initSelectUserButton();
            initSelectedUsersTable();
        }
    }

    protected void initMatchingUsersTable() {
        matchingUsersTable = new Table();
        matchingUsersTable.setColumnHeaderMode(Table.COLUMN_HEADER_MODE_HIDDEN);
        matchingUsersTable.setSelectable(true);
        matchingUsersTable.setEditable(false);
        matchingUsersTable.setImmediate(true);
        matchingUsersTable.setNullSelectionAllowed(false);
        matchingUsersTable.setSortDisabled(true);

        if (multiSelect) {
            matchingUsersTable.setMultiSelect(true);
        }

        matchingUsersTable.addGeneratedColumn("icon", new ThemeImageColumnGenerator(Images.USER_16));
        matchingUsersTable.setColumnWidth("icon", 16);
        matchingUsersTable.addContainerProperty("userName", String.class, null);

        matchingUsersTable.setWidth(300, UNITS_PIXELS);
        matchingUsersTable.setHeight(200, UNITS_PIXELS);
        userSelectionLayout.addComponent(matchingUsersTable);
    }

    protected void initSelectUserButton() {
        selectUserButton = new Button(">");

        selectUserButton.addListener(new ClickListener() {
            public void buttonClick(ClickEvent event) {
                for (String selectedItemId : (Set<String>) matchingUsersTable.getValue()) {
                    // Remove from left table
                    Item originalItem = matchingUsersTable.getItem(selectedItemId);

                    // And put it in right table
                    selectUser(selectedItemId, (String) originalItem.getItemProperty("userName").getValue());

                    // Remove from left table (must be done on the end, or item properties will be inaccessible)
                    matchingUsersTable.removeItem(selectedItemId);
                }
            }
        });

        userSelectionLayout.addComponent(selectUserButton);
        userSelectionLayout.setComponentAlignment(selectUserButton, Alignment.MIDDLE_CENTER);
    }

    protected void initSelectedUsersTable() {
        selectedUsersTable = new Table();
        selectedUsersTable.setColumnHeaderMode(Table.COLUMN_HEADER_MODE_HIDDEN);
        selectedUsersTable.setEditable(false);
        selectedUsersTable.setSortDisabled(true);

        // Icon column
        selectedUsersTable.addGeneratedColumn("icon", new ThemeImageColumnGenerator(Images.USER_ADD));
        selectedUsersTable.setColumnWidth("icon", 16);

        // Name column
        selectedUsersTable.addContainerProperty("userName", String.class, null);

        // Role column
        if (showRoles) {
            selectedUsersTable.addContainerProperty("role", ComboBox.class, null);
        }

        // Delete icon column
        selectedUsersTable.addGeneratedColumn("delete", new ThemeImageColumnGenerator(Images.DELETE,
                new com.vaadin.event.MouseEvents.ClickListener() {
                    public void click(com.vaadin.event.MouseEvents.ClickEvent event) {
                        Object itemId = ((Embedded) event.getSource()).getData();

                        // Add to left table (if possible)
                        String searchFieldValue = (String) searchField.getValue();
                        if (searchFieldValue != null && searchFieldValue.length() >= 2) {
                            String userName = (String) selectedUsersTable.getItem(itemId).getItemProperty("userName").getValue();
                            if (matchesSearchField(userName)) {
                                Item item = matchingUsersTable.addItem(itemId);
                                item.getItemProperty("userName").setValue(userName);
                            }
                        }

                        // Delete from right table
                        selectedUsersTable.removeItem(itemId);
                    }
                }));
        selectedUsersTable.setColumnWidth("icon", 16);

        if (showRoles) {
            selectedUsersTable.setWidth(420, UNITS_PIXELS);
        } else {
            selectedUsersTable.setWidth(300, UNITS_PIXELS);
        }
        selectedUsersTable.setHeight(200, UNITS_PIXELS);
        userSelectionLayout.addComponent(selectedUsersTable);
    }

    protected boolean matchesSearchField(String text) {
        for (String userNameToken : text.split(" ")) {
            if (userNameToken.toLowerCase().startsWith(((String) searchField.getValue()).toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    protected void selectUser(String userId, String userName) {
        if (!selectedUsersTable.containsId(userId)) {
            Item item = selectedUsersTable.addItem(userId);
            item.getItemProperty("userName").setValue(userName);

            if (showRoles) {
                ComboBox comboBox = new ComboBox(null, Arrays.asList(
                        i18nManager.getMessage(Messages.TASK_ROLE_CONTRIBUTOR),
                        i18nManager.getMessage(Messages.TASK_ROLE_IMPLEMENTER),
                        i18nManager.getMessage(Messages.TASK_ROLE_MANAGER),
                        i18nManager.getMessage(Messages.TASK_ROLE_SPONSOR)));
                comboBox.select(i18nManager.getMessage(Messages.TASK_ROLE_CONTRIBUTOR));
                comboBox.setNewItemsAllowed(true);
                item.getItemProperty("role").setValue(comboBox);
            }
        }
    }

    protected void initDoneButton() {
        doneButton = new Button("Done");

        doneButton.addListener(new ClickListener() {
            public void buttonClick(ClickEvent event) {
                // Fire event such that depending UI's can be updated
                fireEvent(new SubmitEvent(doneButton, SubmitEvent.SUBMITTED));

                // close popup window
                close();
            }
        });

        addComponent(doneButton);
        windowLayout.setComponentAlignment(doneButton, Alignment.MIDDLE_RIGHT);
    }

    public String getSelectedUserId() {
        if (multiSelect) {
            throw new ActivitiException("Only use getSelectedUserId in non-multiselect mode");
        }
        return (String) matchingUsersTable.getValue();
    }

    @SuppressWarnings("unchecked")
    public Collection<String> getSelectedUserIds() {
        if (!multiSelect) {
            throw new ActivitiException("Only use getSelectedUserIds in multiselect mode");
        }
        return (Collection<String>) selectedUsersTable.getItemIds();
    }

    public String getSelectedUserRole(String userId) {
        if (!multiSelect) {
            throw new ActivitiException("Only use getSelectedUserIds in multiselect mode");
        }
        return (String) ((ComboBox) selectedUsersTable.getItem(userId).getItemProperty("role").getValue()).getValue();
    }

}
